/**
 * 
 */
package ro.dta.idbi.ui.patterns;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JLabel;

import ro.dta.idbi.interfaces.sql.ISqlColumn;
import ro.dta.idbi.interfaces.sql.ISqlConstruct;
import ro.dta.idbi.interfaces.sql.ISqlTable;
import ro.dta.idbi.interfaces.ui.IMainFrame;
import ro.dta.idbi.patterns.AddSubset;
import ro.dta.idbi.ui.sql.SqlGraph;
import ro.dta.idbi.ui.sql.SqlTableCell;
import uk.ac.ic.doc.automed.reps.Schema;

import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxGraph;

/**
 * UI for the <i>is-a introduction pattern</i>
 * 
 * @author Tudor Dobrila
 * 
 */
public class AddSubsetDialog extends AbstractPatternDialog {

	private static final long serialVersionUID = 345071165631745920L;

	private final static Logger LOGGER = Logger
			.getLogger("com.dta.idbi.patterns.ui.AddSubsetDialog");

	private transient boolean edgeAdded;

	/**
	 * Check if the current pattern can be applied over a given set of objects
	 * 
	 * Used to check if the items selected by the user in the UI can be used to
	 * apply this transformation pattern over them
	 * 
	 * @param schema
	 *            Schema in the context of which to check the validity of the
	 *            array of objects
	 * @param objects
	 *            Set of objects
	 * @return True if pattern can be applied over set of objects, false
	 *         otherwise
	 */
	public static boolean isValid(final Schema schema, final Object[] objects) {
		if (objects.length != 2) {
			return false;
		}

		if (!(objects[0] instanceof ISqlTable) || !(objects[1] instanceof ISqlTable)) {
			return false;
		}

		// Check if the two tables have the same sets of columns in the PK
		// constraints, so a is-a relationship can be added between them
		final ISqlTable table1 = (ISqlTable) objects[0];
		final ISqlTable table2 = (ISqlTable) objects[1];
		final List<String> pks1 = getPKNames(table1.getPrimaryKey());
		final List<String> pks2 = getPKNames(table2.getPrimaryKey());

		if (pks1.size() == pks2.size()) {
			for (String pk : pks1) {
				if (!pks2.contains(pk)) {
					return false;
				}
			}
		} else {
			return false;
		}

		return true;
	}

	/**
	 * Convert from a list of columns to a list of the names of the columns
	 * 
	 * @param pks
	 *            List of columns to convert
	 * @return List of the names of the columns
	 */
	public static List<String> getPKNames(final List<ISqlColumn> pks) {
		final List<String> pkStr = new ArrayList<String>();
		for (ISqlColumn pk : pks) {
			pkStr.add(pk.getValue().toString());
		}
		return pkStr;
	}

	/**
	 * Add subset dialog class constructor
	 * 
	 * @param parentFrame
	 *            Parent frame of the application
	 * @param schema
	 *            Schema over which the transformation is applied
	 * @param element
	 *            Sql construct(s) over which the transformation is applied
	 */
	public AddSubsetDialog(IMainFrame parentFrame, Schema schema, ISqlConstruct[] element) {
		super(parentFrame, schema, element, null, null);
	}

	/**
	 * Mandatory column and total generalisation dialog class constructor
	 * 
	 * @param parentFrame
	 *            Parent frame of this dialog
	 * @param schema
	 *            Schema over which the transformation is applied
	 * @param element
	 *            Sql construct(s) over which the transformation is applied
	 * @param beforeLabel
	 *            Label for the before diagram
	 * @param afterLabel
	 *            Label for the transformed diagram
	 */
	public AddSubsetDialog(final IMainFrame parentFrame, final Schema schema,
			final ISqlConstruct[] element, final JLabel beforeLabel, final JLabel afterLabel) {
		super(parentFrame, schema, element, beforeLabel, afterLabel);

		if (!isValid(schema, element)) {
			throw new IllegalArgumentException(
					"This pattern can't be applied to the given elements");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.ui.patterns.AbstractTransDialog#createOriginalDiagram()
	 */
	@Override
	protected mxGraphComponent createOriginalDiagram() {
		final SqlGraph graph = new SqlGraph();
		addOriginalTables(graph);
		return new mxGraphComponent(graph);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * ro.dta.idbi.ui.patterns.AbstractTransDialog#createTransformedDiagram()
	 */
	@Override
	protected mxGraphComponent createTransformedDiagram() {
		final SqlGraph graph = new SqlGraph();
		graph.setCellsMovable(false);
		addOriginalTables(graph);
		graph.addListener(mxEvent.CELL_CONNECTED, new mxIEventListener() {

			@Override
			public void invoke(final Object arg0, final mxEventObject arg1) {
				final mxCell edge = (mxCell) arg1.getProperty("edge");
				if (!edgeAdded && edge.getSource() != null && edge.getTarget() != null) {
					if (!(edge.getSource() instanceof ISqlTable)
							|| !(edge.getTarget() instanceof ISqlTable)) {
						graph.getModel().beginUpdate();
						graph.getModel().remove(edge);
						graph.getModel().endUpdate();
					} else {
						edgeAdded = true;
						macroField.setText(AddSubset.COMMAND + "(<<" + edge.getTarget().getValue()
								+ ">>, <<" + edge.getSource().getValue() + ">>)");
					}
				}
			}
		});
		final mxGraphComponent gComp = new mxGraphComponent(graph);
		gComp.setConnectable(true);
		return gComp;
	}

	/**
	 * Add the two original tables to the graph
	 * 
	 * @param graph
	 *            Graph where to add the original tables
	 * @return The tables introduced in the graph
	 */
	public ISqlTable[] addOriginalTables(final mxGraph graph) {
		ISqlTable[] result = new ISqlTable[2];
		graph.getModel().beginUpdate();

		try {
			final SqlTableCell newTable1 = (SqlTableCell) element[0].clone();
			mxGeometry newGeometry = new mxGeometry(5, 5, element[0].getGeometry().getWidth(),
					element[0].getGeometry().getHeight());
			newTable1.setGeometry(newGeometry);
			newTable1.layout(graph);
			result[0] = newTable1;

			final SqlTableCell newTable2 = (SqlTableCell) element[1].clone();
			newGeometry = new mxGeometry(200, 5, element[1].getGeometry().getWidth(), element[1]
					.getGeometry().getHeight());
			newTable2.setGeometry(newGeometry);
			newTable2.layout(graph);
			result[1] = newTable2;
		} catch (CloneNotSupportedException e) {
			LOGGER.log(Level.SEVERE, "Fatal error", e);
		} finally {
			graph.setAutoSizeCells(true);
			graph.getModel().endUpdate();
		}

		return result;
	}

}
